/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.db;

import blueprint4j.db.*;
import blueprint4j.utils.*;

import java.sql.Statement;
import java.sql.ResultSet;
import java.util.Hashtable;
import java.util.Date;

/**
 * This class is used to perform routine maintanance
 * maintanance can be used to trim the table to a certain size
 * This can never ve applied to a Replicating Entity
 */
public class Maintenance implements ThreadScheduable  {
	private Entity entity = null;
	private Integer schedule = null;
	private Date schedule_date = null;
	private Integer schedule_freq= null;
	private Action actions[] =null;
	public static final int TRIM_TABLE=0x1;

	// only maintain tables that are not currentlt maintained
	private static Hashtable maintained = new Hashtable();

	/**
	 * Perform routine maintenance
	 * ACTION = TRIM_TABLE value = AUXILIRY VALUE FOR ACTION
	 * SCHEDULE = EVERY MILLI SECOND PERFORM MAINTENANCE
	 */
	private Maintenance(Entity entity,Action actions[]) {
		synchronized (maintained) {
			if (maintained.containsKey(entity.getTableName())) {
				return;
			}
			maintained.put(entity.getTableName(),new Object());
		}
		this.entity = entity;
		this.actions = actions;
		for (int i= 0;i < actions.length;i++) {
			actions[i].entity = entity;
		}
		entity.addMaintenance(this);
	}

	public Maintenance(Entity entity,Action actions[],int schedule) {
		this(entity,actions);
		this.schedule =new Integer(schedule);
	}

	/**
	 * Schedule this to run for a certain type and a certain frequency
	 * 0 HOURLY 1 DAILY 2 WEEKLY 3 MONTHLY 4 YEARLY
	 */
	public Maintenance(Entity entity,Action actions[],Date schedule_date,int schedule_freq) {
		this(entity,actions);
		this.schedule_date =schedule_date;
		this.schedule_freq = new Integer(schedule_freq);
	}

	public boolean keepAlive() {return true;}

	public int sleepTime() {return schedule.intValue();}

	public void process()
	throws Throwable {
		for (int i = 0;i < actions.length;i++) {
			actions[i].executeAction();
		}
	}

	public void close() throws Exception {}

	public static abstract class Action {
		public Entity entity = null;

		protected final void executeAction() {
			try {
				execute();
			} catch (Throwable th) {
				Log.debug.out(th);
			}
		}

		public abstract void execute() throws Throwable;
	}

	public static class ActionTrim extends Action {
		private int amount = 0;

		/**
		* If there are more than [amount] records in table then delete oldest 
		*/
		public ActionTrim(int amount) {
			this.amount = amount;
		}

		public void execute() throws Throwable {
			Log.trace.out("MAINTANNACE Archive","Performing trimming maintenance on "+entity.getTableName() + ". Trim to " + amount);
			// make sure that there are at leat the amount of rows that needs to be delete
			DBConnection connection = DBTools.getNDC();
			Statement statement = connection.createStatement();
			try {
				ResultSet rs = statement.executeQuery("select count(*) from " + entity.getTableName());
				rs.next();
				long count = Long.parseLong(rs.getString(1));
				if (count > amount*2) {
					// ONLY APPLED TO MYSQL
					if (!(connection.getDataBaseUtils() instanceof DataBaseUtilsMySQL)) {
						throw new DataException("Maintenance Action Trim has only been coded for Mysql");
					}
					statement.execute("delete from "+entity.getTableName()+" order by "+entity.id.getName()+" asc limit "+(count-amount));
					// now optimize table
					connection.getDataBaseUtils().executeOptimizeTable(connection,entity.getTableName());
					connection.commit();
				}
				Log.trace.out("MAINTANNACE Archive","Completed Trimming");
			} finally {
				statement.close();
				connection.close();
			}
		}
	}

	public static class ActionArchive extends Action {
		private Entity from= null;
		String to =null;
		private Field dateselect=null;
		private long cutoff = 0;
		private Hashtable archived = new Hashtable();

		/**
		 * All records references from dateselect older then now() - cutoff will be archived
		 */
		public ActionArchive(Entity from,String to,Field dateselect,long cutoff) {
			this.from =from;
			this.to =to;
			this.dateselect = dateselect;
			this.cutoff = cutoff;
			synchronized (archived) {
				if (!archived.containsKey(from.getTableName())) {
					archived.put(from.getTableName(),new Object());
					try {
						DBTools.getDLC().getDataBaseUtils().updateEntity(
						   DBTools.getDLC(),
						   from,
							false
						);
						String from_tabname = from.getTableName();
						from.setTableName(to);
						DBTools.getDLC().getDataBaseUtils().updateEntity(
						   DBTools.getDLC(),
						   from,
							false
						);
						from.setTableName(from_tabname);
					} catch (Throwable th) {
						th.printStackTrace();
						Log.critical.out(th);
					}
				}
			}
		}

		public void execute() throws Throwable {
			DBConnection dbcon = DBTools.getNDC();
			Statement smt = dbcon.createStatement();
			try {
				if (!(dbcon.getDataBaseUtils() instanceof DataBaseUtilsMySQL)) {
					throw new DataException("Maintenance Action Archive has only been coded for Mysql");
				}
				String fieldnames = from.getFields().getNames().toString(",");
				Log.trace.out("MAINTANNACE Archive","Performing archiving maintenance on "+from.getTableName() + " to " + to);
				Log.trace.out("MAINTANNACE Archive","insert into " + to+ "("+
				              from.getFields().getNames().toString(",")+")"+
				              "select * from "+ from.getTableName() +" " +
				              "where "+ dateselect.getName() + " <= " + entity.asSQL(new Date(System.currentTimeMillis() -cutoff))
				             );
				smt.execute(
				   "insert into " + to+ "("+fieldnames+")"+
				   "select "+fieldnames+" from "+ from.getTableName() +" " +
				   "where "+ dateselect.getName() + " <= " + entity.asSQL(new Date(System.currentTimeMillis() -cutoff))
				);
				smt.execute(
				   "delete from " + from.getTableName() + " where "+
				   dateselect.getName() + " <= " + entity.asSQL(new Date(System.currentTimeMillis() -cutoff))
				);
				dbcon.getDataBaseUtils().executeOptimizeTable(dbcon,entity.getTableName());
				dbcon.commit();
			} finally {
				smt.close();
				dbcon.close();
			}

		}
	}

	public static class VectorMaintenance {
		private java.util.Vector store = new java.util.Vector();

		public VectorMaintenance(){}

		public void start() {
			for (int i =0;i < size();i++) {
				if (get(i).schedule !=null) {
					ThreadSchedule.add(get(i));
				} else {
					ThreadSchedule.add(get(i),get(i).schedule_freq.intValue(),get(i).schedule_date,false,Thread.NORM_PRIORITY);
				}
			}
		}

		public Maintenance get(int pos) {
			return (Maintenance)store.get(pos);
		}

		public void add(Maintenance item) {
			store.add(item);
		}

		public boolean remove(Maintenance item) {
			return store.remove(item);
		}

		public Maintenance remove(int pos) {
			return (Maintenance) store.remove(pos);
		}

		public int size() {
			return store.size();
		}

		public void clear(){
			store.clear();
		}
	}

}
